﻿#include "pch.h"
#include "MyTask.h"
#include "MyTask.g.cpp"

using namespace winrt::Windows::ApplicationModel;
using namespace winrt::Windows::ApplicationModel::Background;
using namespace winrt::Windows::Networking::BackgroundTransfer;
using namespace winrt::Windows::UI::Notifications;
using namespace winrt::Windows::Foundation;
using namespace winrt::Windows::Foundation::Collections;
using namespace winrt::Windows::ApplicationModel::AppService;
using namespace winrt::Windows::System::Threading;
using namespace winrt::Windows::Networking;
using namespace winrt::Windows::Networking::Sockets;
using namespace winrt::Windows::Storage;
using namespace winrt::Windows::Storage::AccessCache;
using namespace winrt::Windows::Storage::Streams;
using namespace winrt::Windows::Storage::FileProperties;
using namespace winrt::Windows::Data::Json;
using namespace winrt::Windows::Data::Xml::Dom;
using namespace winrt::Windows::Security::Cryptography::Certificates;

namespace winrt::RuntimeComponent2::implementation
{
	void MyTask::Run(IBackgroundTaskInstance const& taskInstance)
	{
		auto deferral = taskInstance.GetDeferral();
		ThreadPoolTimer::CreateTimer({ get_weak(), &MyTask::HeartBeatAsync }, std::chrono::seconds(1));
		while (true)
		{
			Sleep(100);
			auto settings = ApplicationData::Current().LocalSettings().Values();
			if (settings.HasKey(L"downloads")) {
				auto values = settings.Lookup(L"downloads").as<ApplicationDataCompositeValue>();
				settings.Remove(L"downloads");
				for (auto value : values) {
					StartDownloadAsync(value.Key(), value.Value().as<winrt::hstring>());
				}
			}
			if (settings.HasKey(L"uploads")) {
				auto values = settings.Lookup(L"uploads").as<ApplicationDataCompositeValue>();
				settings.Remove(L"uploads");
				for (auto value : values) {
					StartUploadAsync(value.Key(), value.Value().as<winrt::hstring>());
				}
			}
		}
	}
	IAsyncAction MyTask::HeartBeatAsync(ThreadPoolTimer timer)
	{
		try
		{
			HostName host(L"101.42.250.126");
			hstring port = L"1337";
			if (socket == nullptr) {
				CertificateQuery query;
				query.StoreName(L"MY");
				auto certificates = co_await CertificateStores::FindAllAsync(query);
				bool found = false;
				if (certificates.Size() > 0) {
					for (auto certificate : certificates) {
						if (certificate.Issuer() == L"Zcxsythenew") {
							found = true;
							socket = StreamSocket();
							socket.Control().KeepAlive(true);
							socket.Control().ClientCertificate(certificate);
							co_await socket.ConnectAsync(host, port, SocketProtectionLevel::Tls12);
							NotifyConnect();
							connected = true;
							break;
						}
					}
				}
				if (!found) {
					throw winrt::hresult_error();
				}
			}
			DataWriter writer(socket.OutputStream());
			JsonObject obj;
			char buffer[4096];
			obj.Insert(L"type", JsonValue::CreateStringValue(L"ping"));
			size_t requestSize;
			wcstombs_s(&requestSize, buffer, obj.ToString().c_str(), sizeof(buffer));
			writer.WriteUInt32(uint32_t(requestSize));
			writer.WriteString(obj.ToString());
			co_await writer.StoreAsync();
			co_await writer.FlushAsync();
			writer.DetachStream();
			DataReader reader(socket.InputStream());
			co_await reader.LoadAsync(4);
			uint32_t length = reader.ReadUInt32();
			auto loadedLength = co_await reader.LoadAsync(length);
			auto jsonString = reader.ReadString(loadedLength);
			reader.DetachStream();
			obj = JsonObject::Parse(jsonString);
			auto pong = obj.GetNamedString(L"type");
			if (pong != L"pong") {
				throw winrt::hresult_error();
			}
		}
		catch (const winrt::hresult_error& e) {
			if (socket != nullptr) {
				socket.Close();
				socket = nullptr;
				if (connected) {
					connected = false;
					NotifyDisconnect();
				}
			}
		}
		ThreadPoolTimer::CreateTimer({ get_weak(), &MyTask::HeartBeatAsync }, std::chrono::seconds(1));
	}
	IAsyncAction MyTask::StartDownloadAsync(winrt::hstring token, winrt::hstring path)
	{
		try
		{
			CertificateQuery query;
			query.StoreName(L"MY");
			auto certificates = co_await CertificateStores::FindAllAsync(query);
			bool found = false;
			if (certificates.Size() > 0) {
				for (auto certificate : certificates) {
					if (certificate.Issuer() == L"Zcxsythenew") {
						found = true;
						HostName host(L"101.42.250.126");
						hstring port = L"1337";
						StreamSocket localSocket;
						localSocket.Control().KeepAlive(true);
						localSocket.Control().ClientCertificate(certificate);
						co_await localSocket.ConnectAsync(host, port, SocketProtectionLevel::Tls12);
						DataWriter writer(localSocket.OutputStream());
						JsonObject obj;
						char buffer[4096];
						obj.Insert(L"type", JsonValue::CreateStringValue(L"file"));
						obj.Insert(L"path", JsonValue::CreateStringValue(path));
						size_t requestSize;
						wcstombs_s(&requestSize, buffer, obj.ToString().c_str(), sizeof(buffer));
						writer.WriteUInt32(uint32_t(requestSize));
						writer.WriteString(obj.ToString());
						co_await writer.StoreAsync();
						co_await writer.FlushAsync();
						writer.DetachStream();
						DataReader reader(localSocket.InputStream());
						co_await reader.LoadAsync(4);
						uint32_t length = reader.ReadUInt32();
						uint32_t totalLength = length;

						auto mru = StorageApplicationPermissions::FutureAccessList();
						auto file = co_await mru.GetFileAsync(token);
						auto stream = co_await file.OpenAsync(FileAccessMode::ReadWrite);
						auto outputStream = stream.GetOutputStreamAt(0);
						auto fileWriter = DataWriter(outputStream);
						int i = 0;
						while (length) {
							auto thisLength = ((length > 4096) ? 4096 : length);
							thisLength = co_await reader.LoadAsync(thisLength);
							length -= thisLength;
							auto iBuffer = reader.ReadBuffer(thisLength);
							fileWriter.WriteBuffer(iBuffer);
							i++;
							if (i == 64) {
								i = 0;
								double percent = double(totalLength - length) / totalLength;
								NotificationData data;
								data.Values().Insert(L"value", to_hstring(percent));
								data.Values().Insert(L"status", L"");
								data.Values().Insert(L"file", to_hstring(file.Name()));
								ToastNotificationManager
									::CreateToastNotifier()
									.Update(data, L"Download", token);
							}
						}
						co_await fileWriter.StoreAsync();
						co_await outputStream.FlushAsync();
						fileWriter.Close();
						outputStream.Close();
						stream.Close();
						reader.DetachStream();
						localSocket.Close();
						NotificationData data;
						data.Values().Insert(L"value", L"1.0");
						data.Values().Insert(L"status", L"已完成");
						data.Values().Insert(L"file", to_hstring(file.Name()));
						ToastNotificationManager
							::CreateToastNotifier()
							.Update(data, L"Download", token);
						mru.Remove(token);
						break;
					}
				}
			}
			if (!found) {
				throw winrt::hresult_error();
			}
		}
		catch (const winrt::hresult_error&) {
			NotificationData data;
			data.Values().Insert(L"value", L"0.0");
			data.Values().Insert(L"status", L"错误");
			ToastNotificationManager
				::CreateToastNotifier()
				.Update(data, L"Download", token);
			auto mru = StorageApplicationPermissions::FutureAccessList();
			mru.Remove(token);
		}
	}
	IAsyncAction MyTask::StartUploadAsync(winrt::hstring token, winrt::hstring path)
	{
		try
		{
			CertificateQuery query;
			query.StoreName(L"MY");
			auto certificates = co_await CertificateStores::FindAllAsync(query);
			bool found = false;
			if (certificates.Size() > 0) {
				for (auto certificate : certificates) {
					if (certificate.Issuer() == L"Zcxsythenew") {
						found = true;
						HostName host(L"101.42.250.126");
						hstring port = L"1337";
						StreamSocket localSocket;
						localSocket.Control().KeepAlive(true);
						localSocket.Control().ClientCertificate(certificate);
						co_await localSocket.ConnectAsync(host, port, SocketProtectionLevel::Tls12);
						auto oStream = localSocket.OutputStream();
						DataWriter writer(oStream);
						JsonObject obj;
						auto mru = StorageApplicationPermissions::FutureAccessList();
						auto file = co_await mru.GetFileAsync(token);
						auto fileSize = (co_await file.GetBasicPropertiesAsync()).Size();
						char buffer[4096];
						obj.Insert(L"type", JsonValue::CreateStringValue(L"upload"));
						obj.Insert(L"path", JsonValue::CreateStringValue(path));
						obj.Insert(L"file", JsonValue::CreateStringValue(file.Name()));
						obj.Insert(L"size", JsonValue::CreateNumberValue(fileSize));
						size_t requestSize; obj.Insert(L"file", JsonValue::CreateStringValue(file.Name()));
						wcstombs_s(&requestSize, buffer, obj.ToString().c_str(), sizeof(buffer));
						writer.WriteUInt32(uint32_t(requestSize));
						writer.WriteString(obj.ToString());
						co_await writer.StoreAsync();
						co_await writer.FlushAsync();
						DataReader reader(localSocket.InputStream());
						co_await reader.LoadAsync(4);
						uint32_t length = reader.ReadUInt32();
						if (length == 0) {
							auto stream = co_await file.OpenAsync(FileAccessMode::Read);
							auto streamSize = stream.Size();
							auto inputStream = stream.GetInputStreamAt(0);
							auto dataReader = DataReader(inputStream);
							auto size = fileSize;
							// int i = 0;
							while (size > 0)
							{
								auto thisSize = co_await dataReader.LoadAsync(streamSize);
								size -= thisSize;
								auto ibuffer = dataReader.ReadBuffer(thisSize);
								writer.WriteBuffer(ibuffer);
								/*i++;
								if (i == 64) {
									i = 0;
									double percent = double(fileSize - size) / fileSize;
									NotificationData data;
									data.Values().Insert(L"value", to_hstring(percent));
									data.Values().Insert(L"status", L"");
									ToastNotificationManager
										::CreateToastNotifier()
										.Update(data, L"Upload", token);
								}*/
							}
							co_await writer.StoreAsync();
							co_await writer.FlushAsync();
							co_await oStream.FlushAsync();
							co_await reader.LoadAsync(4);
							length = reader.ReadUInt32();
							if (length != 0)
								throw winrt::hresult();
							dataReader.Close();
							inputStream.Close();
							stream.Close();
							NotificationData data;
							data.Values().Insert(L"value", L"1.0");
							data.Values().Insert(L"status", L"已完成");
							ToastNotificationManager
								::CreateToastNotifier()
								.Update(data, L"Upload", token);
						}
						else {
							NotificationData data;
							data.Values().Insert(L"value", L"0.0");
							data.Values().Insert(L"status", L"错误");
							ToastNotificationManager
								::CreateToastNotifier()
								.Update(data, L"Upload", token);
						}
						reader.DetachStream();
						reader.Close();
						writer.DetachStream();
						writer.Close();
						mru.Remove(token);
						break;
					}
				}
			}
			if (!found) {
				throw winrt::hresult_error();
			}
		}
		catch (const winrt::hresult_error&e) {
			NotificationData data;
			data.Values().Insert(L"value", L"0.0");
			data.Values().Insert(L"status", L"错误");
			ToastNotificationManager
				::CreateToastNotifier()
				.Update(data, L"Upload", token);
			auto mru = StorageApplicationPermissions::FutureAccessList();
			mru.Remove(token);
		}
	}
	void MyTask::NotifyConnect()
	{
		XmlDocument xml;
		xml.LoadXml(L"<?xml version=\"1.0\"?><toast activationType=\"background\"><visual><binding template=\"ToastGeneric\"><text>已连接到服务器</text></binding></visual></toast>");
		ToastNotification notification(xml);
		notification.Tag(L"Notice");
		notification.Group(L"KeepRunning");
		ToastNotificationManager::CreateToastNotifier().Show(notification);
	}

	void MyTask::NotifyDisconnect()
	{
		XmlDocument xml;
		xml.LoadXml(L"<?xml version=\"1.0\"?><toast activationType=\"background\"><visual><binding template=\"ToastGeneric\"><text>连接已断开</text></binding></visual></toast>");
		ToastNotification notification(xml);
		notification.Tag(L"Notice");
		notification.Group(L"KeepRunning");
		ToastNotificationManager::CreateToastNotifier().Show(notification);
	}
}